package com.remy.itaxiproto;

import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.Message;
import com.remy.itaxiproto.ITaxiProto.*;


import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public final class ProtobufCodes {
	public static final class MsgPair {
		public MsgHead msgHead = null;
		public Message msg = null;
	}

	public static final class EncodedBuf {
		public byte[] outBytes = null;
		public int outLen = 0;
	}

	static byte[] outBuf = new byte[1024 * 1024];

	public static EncodedBuf Encode(MsgHead msgHead, Message msg) {
		try {
			CodedOutputStream outputStream = CodedOutputStream
					.newInstance(outBuf);
			String typeName = msg.getDescriptorForType().getName();

			MsgHead tempMsgHead = msgHead.toBuilder().setMsgTypeName(typeName)
					.build();

			short headLen = (short) (tempMsgHead.getSerializedSize() + 2);
			int totalLen = 4 + headLen + msg.getSerializedSize() + 4;
			outputStream.writeRawBytes(int32ToBytes(totalLen));
			byte[] headLenBytes = new byte[] { (byte) ((headLen >> 8) & 0xFF),
					(byte) (headLen & 0xFF) };
			outputStream.writeRawBytes(headLenBytes);
			tempMsgHead.writeTo(outputStream);
			msg.writeTo(outputStream);
			int checkSum = CheckSum(outBuf, 0, totalLen - 4);
			outputStream.writeRawBytes(int32ToBytes(checkSum));
			EncodedBuf encodedBuf = new EncodedBuf();
			encodedBuf.outBytes = outBuf;
			encodedBuf.outLen = totalLen;
			return encodedBuf;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	public static MsgPair Decode(byte[] inBuff) {
		try {
			if (inBuff.length < 10) {
				return null;
			}

			CodedInputStream inputStream = CodedInputStream.newInstance(inBuff,
					0, 6);
			int totalLen = bytesToInt(inputStream.readRawBytes(4), 0);
			if (totalLen < 10 || totalLen > 1024 * 1024)
				return null;
			short headLen = bytesToShort(inputStream.readRawBytes(2), 0);
			if (headLen > totalLen - 8)
				return null;
			int checkSum = CheckSum(inBuff, 0, totalLen - 4);
			int readedCheckSum = bytesToInt(inBuff, totalLen - 4);
			if (checkSum != readedCheckSum)
				return null;
			MsgPair msgPair = new MsgPair();
			msgPair.msgHead = MsgHead.newBuilder()
					.mergeFrom(inBuff, 6, headLen - 2).build();
			String msgTypeName = "com.remy.itaxiproto.ITaxiProto$";
			msgTypeName += msgPair.msgHead.getMsgTypeName();
			Class classType = Class.forName(msgTypeName);
			Message.Builder builder = (Message.Builder) classType.getMethod(
					"newBuilder").invoke(null);

			msgPair.msg = builder.mergeFrom(inBuff, 4 + headLen,
					totalLen - 8 - headLen).build();
			return msgPair;
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	static int CheckSum(byte[] buff, int startIndex, int lenth) {
		int checkSum = 0;
		for (int i = 0; i < lenth / 4; ++i) {
			checkSum += bytesToInt(buff, startIndex + i * 4);
		}
		return checkSum;
	}

	public static int bytesToInt(byte[] bytes, int off) {
		final int mask = 0xff;
		int temp = 0;
		int value = 0;
		for (int i = 0; i < 4; ++i) {
			value <<= 8;
			temp = bytes[off + i] & mask;
			value |= temp;
		}
		return value;
	}

	public static short bytesToShort(byte[] bytes, int off) {

		final int mask = 0xff;
		int temp = 0;
		int value = 0;
		for (int i = 0; i < 2; ++i) {
			value <<= 8;
			temp = bytes[off + i] & mask;
			value |= temp;
		}
		return (short) value;
	}

	public static byte[] int32ToBytes(final int value) {
		byte[] retBytes = new byte[4];
		retBytes[3] = (byte) (value & 0xFF);
		retBytes[2] = (byte) ((value >> 8) & 0xFF);
		retBytes[1] = (byte) ((value >> 16) & 0xFF);
		retBytes[0] = (byte) ((value >> 24) & 0xFF);
		return retBytes;
	}

}
